/*
 * $Author: isan.wong@gmail.com $
 * $Date: 2011-07-29 01:03:06 +0000 (Fri, 29 Jul 2011) $
 * $Rev: 13 $
 * $URL: http://capitalbio-smd.googlecode.com/svn/src/com/capitalbio/smd/base/Sample.java $
 */

package com.capitalbio.smd.base;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.biojava.bio.BioError;
import org.biojava.bio.BioException;
import org.biojava.bio.chromatogram.UnsupportedChromatogramFormatException;
import org.biojava.bio.seq.Sequence;
import org.biojava.bio.seq.impl.SimpleSequence;
import org.biojava.bio.symbol.Alignment;
import org.biojava.bio.symbol.IllegalSymbolException;
import org.biojava.bio.symbol.SimpleAlignment;
import org.biojava.bio.symbol.SymbolList;
import com.capitalbio.hla.base.Chromat;

public class Sample {
	protected Log log = LogFactory.getLog(this.getClass());

	// Sample analysis status
	public static final int STATUS_UNKNOWN = -1;
	public static final int STATUS_READY = 0;
	public static final int STATUS_ANALYZING = 1;
	public static final int STATUS_ANALYZED_SUCCESS = 2;
	public static final int STATUS_ANALYZED_FAIL = 3;
	public static final int STATUS_OPTIMIZED_SUCCESS = 4;
	public static final int STATUS_OPTIMIZED_FAIL = 5;

	// Alignment labels
	public static final Object LABEL_STANDARD = "LABEL_STANDARD";
	public static final Object LABEL_MIXED = "LABEL_MIXED";
	public static final Object LABEL_CHROMOSOME1 = "LABEL_CHROMOSOME1";
	public static final Object LABEL_CHROMOSOME2 = "LABEL_CHROMOSOME2";

	private String name;
	private Chromat chromat;
	private Result result;
	private Properties properties;
	private int status = STATUS_READY;
	
	/**
	 * <p>
	 * Create a new sample object using the specified sample name, chromatogram file and properties.
	 * </p>
	 * <p>
	 * Usually, for your attention, you should create a new properties object using the project's properties object.
	 * </p>
	 * 
	 * @param name
	 *            sample name
	 * @param chromatogramFile
	 *            chromatogram file
	 * @param properties
	 *            properties generated from project's properties
	 * @throws IOException 
	 * @throws UnsupportedChromatogramFormatException 
	 */
	public Sample(String name, File chromatogramFile, Properties properties) throws UnsupportedChromatogramFormatException, IndexOutOfBoundsException, IllegalSymbolException, IOException, BioException, BioError {
		this(chromatogramFile, properties);
		this.name = name;
	}

	/**
	 * <p>
	 * Create a new sample object using the specified chromatogram file and properties.
	 * </p>
	 * <p>
	 * Usually, for your attention, you should create a new properties object using the project's properties object.
	 * </p>
	 * 
	 * @param chromatogramFile
	 *            峰图文件
	 * @param properties           
	 * @throws IOException 
	 * @throws BioException, BioError 
	 */
	public Sample(File chromatogramFile, Properties properties) throws UnsupportedChromatogramFormatException, IndexOutOfBoundsException, IllegalSymbolException, IOException, BioException, BioError {
		chromat = new Chromat();
		chromat.setCallingMethod(Chromat.CALLING_METHOD_MIXED);
		chromat.setMaxMixedBases(2);
		try {
			chromat.setChromatogramFile(chromatogramFile);
		} catch (IndexOutOfBoundsException e) {
			log.error(e);
			throw e;
		} catch (IllegalSymbolException e) {
			log.error(e);
			throw e;
		} catch (UnsupportedChromatogramFormatException e) {
			log.error(e);
			throw e;
		} catch (IOException e) {
			log.error(e);
			throw e;
		}
		catch (BioException e) {
			log.error(e);
			throw e;
		} catch (BioError e) {
			log.error(e);
			throw e;
		}
		this.properties = properties;
		
		//set trim
		int startTrimLength = Integer.parseInt(properties
				.getProperty("smd.sequence.trim.start"));
		int stopTrimLength = Integer.parseInt(properties
				.getProperty("smd.sequence.trim.stop"));
		chromat.setTrim(startTrimLength, stopTrimLength);
	}

	/**
	 * 获取样品名称, 如果尚未设置样品名称且样品峰图文件存在, 会使用峰图文件名的主文件名作为样品名返回
	 * 
	 * @return 样品名称
	 */
	public String getName() {
		if (name == null && chromat.getChromatogramFile() != null) {
			String[] elements = chromat.getChromatogramFile().getName().split("\\.");
			if (elements.length > 1) {
				StringBuilder mainNameBuilder = new StringBuilder();
				for (int i = 0; i < elements.length - 1; i++) {
					mainNameBuilder.append(elements[i]);
				}
				name = mainNameBuilder.toString();
			}
		}
		return name;
	}

	/**
	 * 设置样品名称
	 * 
	 * @param name
	 *            样品名称
	 */
	public void setName(String name) {
		this.name = name;
	}

	public Result getResult() {
		return result;
	}

	/**
	 * properties of the sample
	 * @return properties
	 */
	public Properties getProperties() {
		return properties;
	}

	/**
	 * set properties of the sample
	 * @param properties
	 */
	public void setProperties(Properties properties) {
		this.properties = properties;
	}

	/**
	 * 优化
	 * @throws Exception 
	 */
	public void optimize(StdGene stdGene) throws Exception {
		analyze(stdGene);
		
		if (status == STATUS_ANALYZED_SUCCESS) {
			
			if (result.getOptimizedVariations() != null) {
				status = STATUS_OPTIMIZED_SUCCESS;
			}
			else {
				status = STATUS_OPTIMIZED_FAIL;
			}
			
			//优化分析结果（等位交换）
			/*
			SymbolList seq1 = result.getSeq1();
			SymbolList seq2 = result.getSeq2();
			List<int[]> gaps1 = Util.findGaps(seq1);
			List<int[]> gaps2 = Util.findGaps(seq2);
			if (gaps1.size() > 0) {
				List<int[]> segments = new ArrayList<int[]>();
				
			}*/
			
		}
		else {
			status = STATUS_OPTIMIZED_FAIL;
		}
	}
	
	/**
	 * 使用当前 properties 分析
	 * @throws Exception 
	 */
	public void analyze(StdGene stdGene) throws Exception {
		status = STATUS_ANALYZING;

		try {
			Alignment alignment = doAlignment(stdGene, chromat.getValidSymbolList());
			SymbolList seq0 = alignment.symbolListForLabel(LABEL_STANDARD);
			SymbolList seq1 = alignment.symbolListForLabel(LABEL_CHROMOSOME1);
			SymbolList seq2 = alignment.symbolListForLabel(LABEL_CHROMOSOME2);

			// ignore terminal gaps?
			int startGapTrimLength = 0;
			int stopGapTrimLength = 0;
			if (Util.getBooleanProperty("smd.alignment.ignore.terminal.gaps")) {
				startGapTrimLength = Util.getMaxStartGapLength(seq0, seq1, seq2);
				stopGapTrimLength = Util.getMaxStopGapLength(seq0, seq1, seq2);

				seq0 = seq0.subList(startGapTrimLength + 1, seq0.length() - stopGapTrimLength);
				seq1 = seq1.subList(startGapTrimLength + 1, seq1.length() - stopGapTrimLength);
				seq2 = seq2.subList(startGapTrimLength + 1, seq2.length() - stopGapTrimLength);
			}

			int offset = startGapTrimLength + stdGene.getConcernedRegions().get(0)[0] - 1;
			if (stdGene.getConcernedRegions().size() > 1) {
				List<Integer> regionAccs = new ArrayList<Integer>();
				regionAccs.add(0);
				for (int i = 0; i < stdGene.getConcernedRegions().size() - 1; i++) {
					int[] region = stdGene.getConcernedRegions().get(i);
					regionAccs.add(regionAccs.get(i) + region[1] - region[0]);
				}
				
				for (int i = regionAccs.size() - 1; i >= 0; i--) {
					//log.debug("**********" + startGapTrimLength + " " + regionAccs.get(i));
					if (startGapTrimLength > regionAccs.get(i)) {
						offset = startGapTrimLength + stdGene.getConcernedRegions().get(i)[0] - 1;
						break;
					}
				}
			}
			
			Result newResult = new Result(
				new Properties(properties),
				stdGene,
				chromat.getSymbolList(),
				seq0,
				seq1,
				seq2,
				offset
			);

			setResult(newResult);
			
			status = STATUS_ANALYZED_SUCCESS;
		} catch (Exception e) {
			status = STATUS_ANALYZED_FAIL;
			log.error(e);
			throw e;
		}
	}

	private Alignment doAlignment(StdGene stdGene, SymbolList symbolList) throws Exception {
		try {
			// 第一次比对
			Alignment alignment1 = doAlignment1(stdGene, symbolList);

			SymbolList stdSeq_a1 = alignment1.symbolListForLabel(LABEL_STANDARD);;
			SymbolList seq_a1 = alignment1.symbolListForLabel(LABEL_MIXED);;

			// 序列相减
			List<SymbolList> seq_a1_s = Util.subtractSeq(seq_a1, stdSeq_a1);

			// 再次比对

			SymbolList seqChrom1 = seq_a1_s.get(0);
			SymbolList seqChrom2 = seq_a1_s.get(1);

			Alignment alignment2 = doAlignment2(stdGene, seqChrom1, seqChrom2);
			return alignment2;
			
		} catch (Exception e) {
			log.error(e);
			throw e;
		}
	}
	
	private Alignment doAlignment1(StdGene stdGene, SymbolList symbolList) throws Exception {
		try {
			// 第一次比对
			Clustal clustal1 = new Clustal(getName() + "_1");

			Sequence stdSeq = new SimpleSequence(stdGene.getSymbolList(),
					"standard", "standard", null);
			Sequence seq = new SimpleSequence(chromat.getValidSymbolList(), "sample",
					"sample", null);

			clustal1.addSequence(stdSeq);
			clustal1.addSequence(seq);

			// 比对参数
			String PWMATRIX_1 = Util
					.getMatrixProperty("smd.alignment.1.pwmatrix");
			String MATRIX_1 = Util.getMatrixProperty("smd.alignment.1.matrix");
			float PWGAPOPEN_1 = Util
					.getFloatProperty("smd.alignment.1.pwgapopen");
			float PWGAPEXT_1 = Util
					.getFloatProperty("smd.alignment.1.pwgapext");
			float GAPOPEN_1 = Util.getFloatProperty("smd.alignment.1.gapopen");
			float GAPEXT_1 = Util.getFloatProperty("smd.alignment.1.gapext");

			clustal1.setPWMATRIX(PWMATRIX_1);
			clustal1.setPWGAPOPEN(PWGAPOPEN_1);
			clustal1.setPWGAPEXT(PWGAPEXT_1);

			clustal1.setMATRIX(MATRIX_1);
			clustal1.setGAPOPEN(GAPOPEN_1);
			clustal1.setGAPEXT(GAPEXT_1);

			clustal1.doMultAlign();

			Sequence stdSeq_a1 = clustal1.getSequenceDB().getSequence(
					stdSeq.getName());
			Sequence seq_a1 = clustal1.getSequenceDB().getSequence(
					seq.getName());

			Map<Object, SymbolList> labelToSymbolList = new HashMap<Object, SymbolList>();
			labelToSymbolList.put(LABEL_STANDARD, stdSeq_a1);
			labelToSymbolList.put(LABEL_MIXED, seq_a1);
			Alignment alignment = new SimpleAlignment(labelToSymbolList);

			return alignment;
		} catch (Exception e) {
			throw e;
		}
	}
	
	private Alignment doAlignment2(StdGene stdGene, SymbolList chrom1, SymbolList chrom2) throws Exception {
		try {
			// 再次比对
			Clustal clustal2 = new Clustal(getName() + "_2");

			Sequence stdSeq = new SimpleSequence(stdGene.getSymbolList(),
					"standard", "standard", null);
			Sequence seqChrom1 =  new SimpleSequence(chrom1,
					"chrom1", "chrom1", null);
			Sequence seqChrom2 =  new SimpleSequence(chrom2,
					"chrom2", "chrom2", null);
			
			clustal2.addSequence(stdSeq);
			clustal2.addSequence(seqChrom1);
			clustal2.addSequence(seqChrom2);

			String PWMATRIX_2 = Util
					.getMatrixProperty("smd.alignment.2.pwmatrix");
			String MATRIX_2 = Util.getMatrixProperty("smd.alignment.2.matrix");
			float PWGAPOPEN_2 = Util
					.getFloatProperty("smd.alignment.2.pwgapopen");
			float PWGAPEXT_2 = Util
					.getFloatProperty("smd.alignment.2.pwgapext");
			float GAPOPEN_2 = Util.getFloatProperty("smd.alignment.2.gapopen");
			float GAPEXT_2 = Util.getFloatProperty("smd.alignment.2.gapext");

			clustal2.setPWMATRIX(PWMATRIX_2);
			clustal2.setPWGAPOPEN(PWGAPOPEN_2);
			clustal2.setPWGAPEXT(PWGAPEXT_2);

			clustal2.setMATRIX(MATRIX_2);
			clustal2.setGAPOPEN(GAPOPEN_2);
			clustal2.setGAPEXT(GAPEXT_2);

			clustal2.doMultAlign();

			Sequence stdSeq_a2 = clustal2.getSequenceDB().getSequence(
					stdSeq.getName());
			Sequence seqChrom1_a2 = clustal2.getSequenceDB().getSequence(
					seqChrom1.getName());
			Sequence seqChrom2_a2 = clustal2.getSequenceDB().getSequence(
					seqChrom2.getName());

			Map<Object, SymbolList> labelToSymbolList = new HashMap<Object, SymbolList>();
			labelToSymbolList.put(LABEL_STANDARD, stdSeq_a2);
			labelToSymbolList.put(LABEL_CHROMOSOME1, seqChrom1_a2);
			labelToSymbolList.put(LABEL_CHROMOSOME2, seqChrom2_a2);
			Alignment alignment = new SimpleAlignment(labelToSymbolList);

			return alignment;
		} catch (Exception e) {
			log.error(e);
			throw e;
		}
	}

	public void setResult(Result result) {
		this.result = result;
	}

	public int getStatus() {
		return status;
	}

	public void setStatus(int status) {
		this.status = status;
	}

	/*private List<Sequence> doAlignment_Unused(StdGene stdGene, SymbolList symbolList) {

		try {
			// 第一次比对
			Clustal align1 = new Clustal(getName() + "_1");

			// -------------------- 处理 stdGene 的常见变异 begin --------------------
			// 当前只处理了缺失
			SymbolList symbolListWithoutDeletions = getValidSymbolList();
			List<Variation> commonDeletions = stdGene
					.getCommonVariations(VariationType.DELETION);
			if (commonDeletions.size() > 0) {
				// sort common deletions by start location
				Collections.sort(commonDeletions, new Comparator<Variation>() {
					@Override
					public int compare(Variation variation1,
							Variation variation2) {
						if (variation1.getLocation().getType() != variation2
								.getLocation().getType()) {
							throw new IllegalArgumentException(
									"Two variations of different types can not be compared.");
						}
						// sort by start location
						if (variation1.getLocation().getStart() < variation2
								.getLocation().getStart()) {
							return -1;
						} else if (variation1.getLocation().getStart() > variation2
								.getLocation().getStart()) {
							return 1;
						}
						return 0;
					}
				});

				// replace all symbols in common deletions with gap symbol
				Sequence stdGeneSequence = stdGene.getSequence();
				StringBuilder seqBuilder = new StringBuilder();
				for (int i = 0; i < commonDeletions.size(); i++) {
					int lastStop = i == 0 ? 0 : commonDeletions.get(i - 1)
							.getLocation().getStop();
					int start = commonDeletions.get(i).getLocation().getStart();
					int stop = commonDeletions.get(i).getLocation().getStop();

					log.debug(lastStop + "-" + start + "-" + stop);

					seqBuilder.append(stdGeneSequence.subList(lastStop + 1,
							start - 1).seqString());
					for (int j = start; j <= stop; j++) {
						seqBuilder.append("-");
					}
				}
				seqBuilder.append(stdGeneSequence.subList(
						commonDeletions.get(commonDeletions.size() - 1)
								.getLocation().getStop() + 1,
						stdGeneSequence.length()).seqString());
				symbolListWithoutDeletions = new SimpleSymbolList(
						Util.getDNATokenization(), seqBuilder.toString());
			}

			// -------------------- 处理 stdGene 的常见变异 end --------------------

			Sequence stdSeq = new SimpleSequence(symbolListWithoutDeletions,
					"standard", "standard", null);
			Sequence seq = new SimpleSequence(getValidSymbolList(), "sample",
					"sample", null);

			align1.addSequence(stdSeq);
			align1.addSequence(seq);

			// 比对参数
			String PWMATRIX_1 = new File(Util.getResourceURL(
					"matrices/"
							+ properties
									.getProperty("smd.alignment.1.pwmatrix")
							+ ".matrix").toURI()).getAbsolutePath();
			String MATRIX_1 = new File(Util.getResourceURL(
					"matrices/"
							+ properties.getProperty("smd.alignment.1.matrix")
							+ ".matrix").toURI()).getAbsolutePath();
			float PWGAPOPEN_1 = Float.parseFloat(properties
					.getProperty("smd.alignment.1.pwgapopen"));
			float PWGAPEXT_1 = Float.parseFloat(properties
					.getProperty("smd.alignment.1.pwgapext"));
			float GAPOPEN_1 = Float.parseFloat(properties
					.getProperty("smd.alignment.1.gapopen"));
			float GAPEXT_1 = Float.parseFloat(properties
					.getProperty("smd.alignment.1.gapext"));

			align1.setPWMATRIX(PWMATRIX_1);
			align1.setPWGAPOPEN(PWGAPOPEN_1);
			align1.setPWGAPEXT(PWGAPEXT_1);

			align1.setMATRIX(MATRIX_1);
			align1.setGAPOPEN(GAPOPEN_1);
			align1.setGAPEXT(GAPEXT_1);

			align1.doMultAlign();

			Sequence stdSeq_a1 = align1.getSequenceDB().getSequence(
					stdSeq.getName());
			Sequence seq_a1 = align1.getSequenceDB().getSequence(seq.getName());

			// 序列相减
			List<SymbolList> seq_a1_s = Util.subtractSeq(seq_a1, stdSeq_a1);

			// 再次比对
			Clustal align2 = new Clustal(getName() + "_2");

			SymbolList seqStrand1 = seq_a1_s.get(0);
			SymbolList seqStrand2 = seq_a1_s.get(1);

			align2.addSequence(stdSeq);
			align2.addSequence(seqStrand1);
			align2.addSequence(seqStrand2);

			String PWMATRIX_2 = new File(Util.getResourceURL(
					"matrices/"
							+ properties
									.getProperty("smd.alignment.2.pwmatrix")
							+ ".matrix").toURI()).getAbsolutePath();
			String MATRIX_2 = new File(Util.getResourceURL(
					"matrices/"
							+ properties.getProperty("smd.alignment.2.matrix")
							+ ".matrix").toURI()).getAbsolutePath();
			float PWGAPOPEN_2 = Float.parseFloat(properties
					.getProperty("smd.alignment.2.pwgapopen"));
			float PWGAPEXT_2 = Float.parseFloat(properties
					.getProperty("smd.alignment.2.pwgapext"));
			float GAPOPEN_2 = Float.parseFloat(properties
					.getProperty("smd.alignment.2.gapopen"));
			float GAPEXT_2 = Float.parseFloat(properties
					.getProperty("smd.alignment.2.gapext"));

			align2.setPWMATRIX(PWMATRIX_2);
			align2.setPWGAPOPEN(PWGAPOPEN_2);
			align2.setPWGAPEXT(PWGAPEXT_2);

			align2.setMATRIX(MATRIX_2);
			align2.setGAPOPEN(GAPOPEN_2);
			align2.setGAPEXT(GAPEXT_2);

			align2.doMultAlign();

			Sequence strand0 = align2.getSequenceDB().getSequence(
					stdSeq.getName());
			Sequence strand1 = align2.getSequenceDB().getSequence(
					seqStrand1.getName());
			Sequence strand2 = align2.getSequenceDB().getSequence(
					seqStrand2.getName());

			List<Sequence> sequences = new ArrayList<Sequence>();
			sequences.add(strand0);
			sequences.add(strand1);
			sequences.add(strand2);

			return sequences;
		} catch (Exception e) {
			log.error(e);
			return null;
		}
	}*/

	public Chromat getChromat() {
		return chromat;
	}

}
